#include <windowsx.h>
#include <commctrl.h>

#pragma once

#define DEFAULT_TXT_LIM 0x7530 ///< Constant

/// @name Macroes
/// @{

/// @def NELEMS(a)
///
/// @brief Computes number of elements of an array.
///
/// @param a An array.

#define NELEMS(a)  (sizeof(a) / sizeof((a)[0]))
#define ComboBoxEx_GetEditControl(hwnd) (HWND)SNDMSG((hwnd),CBEM_GETEDITCONTROL,0,0)

/// @def Combo_SetEditSel(hwndCtl, fExtended, ichStart,ichEnd)
///
/// @brief Selects characters in the edit control of a combo box.
///
/// @param hwndCtl Handle of the combobox.
/// @param ichStart The starting position. If this is ?, the selection, if any,
///                  is removed.
/// @param ichEnd The ending position. If this is ?, all text from the starting
///                position to the last character in the edit control is selected.

#define Combo_SetEditSel(hwndCtl, ichStart, ichEnd) IsExtended(hwndCtl) ? \
    (Edit_SetSel(ComboBoxEx_GetEditControl(hwndCtl),ichStart,ichEnd),0) : \
    (ComboBox_SetEditSel(hwndCtl,ichStart,ichEnd),0)

/// @}

/// @brief Determine if a combobox is the extended version.
///
/// @param hwndCtl Handle of the control.
///
/// @returns TRUE For the ComboboxEx otherwise FALSE.

static BOOL IsExtended(HWND hwndCtl)
{
    static TCHAR buf[MAX_PATH];
    GetClassName(hwndCtl, buf, MAX_PATH);
    return 0 == _tcsicmp(buf, WC_COMBOBOXEX);
}

/// @brief Finds the first string in a combo box list that begins with the specified string.
///
/// @param hwndCtl Handle of the combobox.
/// @param indexStart The zero-based index of the item before the first item
///                    to be searched. When the search reaches the bottom of the
///                    list, it continues searching from the top of the list back
///                    to the item specified by the indexStart parameter. If
///                    indexStart is ?, the entire list is searched from the
///                    beginning.
/// @param lpszFind The string to find.
///
/// @returns The index of the matching item, or CB_ERR if the search was unsuccessful.

static int Combo_FindString(HWND hwndCtl, INT indexStart, LPTSTR lpszFind)
{
    // Note: ComboBox_FindString does not work with ComboBoxEx and so it is necessary
    //  to furnish our own version of the function.  We will use this version for
    //  both types of comboBoxes.

    TCHAR lpszBuffer[DEFAULT_TXT_LIM];
    TCHAR tmp[DEFAULT_TXT_LIM];
    int ln = _tcslen(lpszFind) + 1;
    if (ln == 1 || indexStart > ComboBox_GetCount(hwndCtl))
        return CB_ERR;

    for (int i = indexStart == -1 ? 0 : indexStart; i < ComboBox_GetCount(hwndCtl); i++)
    {
        ComboBox_GetLBText(hwndCtl, i, lpszBuffer);
        lstrcpyn(tmp, lpszBuffer, ln);
        if (!_tcsicmp(lpszFind, tmp))
            return i;
    }
    return CB_ERR;
}

/// @brief Window procedure for the combobox control.
///
/// @param hwnd Handle of the combobox.
/// @param ch the keyboard char to handle.
///
/// @returns VOID

static void DoAutoComplete(HWND hwnd, TCHAR ch)
{
    // Note: If user presses VK_RETURN then
    //  the ComboBox Notification = CBN_SELENDCANCEL
    //  If the user presses any other key that causes a selection
    //  and closure of the dropdown then
    //  the ComboBox Notification = CBN_SELCHANGE

    static TCHAR buf[DEFAULT_TXT_LIM];
    static TCHAR toFind[DEFAULT_TXT_LIM];
    int index = 0;

    // Handle keyboard input
    if (VK_RETURN == ch)
    {
        ComboBox_ShowDropdown(hwnd, FALSE);
        Combo_SetEditSel(hwnd, 0, -1); //selects the entire item
        ComboBox_GetText(hwnd, buf, NELEMS(buf));
        ComboBox_SetCurSel(hwnd, ComboBox_FindStringExact(hwnd, 0, buf));
    }
    else if (VK_BACK == ch)
    {
        //Backspace normally erases highlighted match
        //  we only want to move the highlighter back a step
        index = ComboBox_GetCurSel(hwnd);
        int bs = LOWORD(ComboBox_GetEditSel(hwnd)) - 1;

        // keep current selection selected
        ComboBox_SetCurSel(hwnd, index);

        // Move cursor back one space to the insertion point for new text
        // and hilight the remainder of the selected match or near match
        Combo_SetEditSel(hwnd, bs, -1);
    }
    else if (!_istcntrl(ch))
    {
        BOOL status = GetWindowLongPtr(hwnd, GWL_STYLE) & CBS_DROPDOWN;
        if (status)
            ComboBox_ShowDropdown(hwnd, TRUE);

        if (IsExtended(hwnd)) // keep focus on edit box
            SetFocus(ComboBoxEx_GetEditControl(hwnd));

        // Get the substring from 0 to start of selection
        ComboBox_GetText(hwnd, buf, NELEMS(buf));
        buf[LOWORD(ComboBox_GetEditSel(hwnd))] = 0;

        _stprintf_s(toFind,// NELEMS(toFind),
#ifdef _UNICODE
            _T("%ls%lc"),
#else
            _T("%s%c"),
#endif
            buf, ch);

        // Find the first item in the combo box that matches ToFind
        index = ComboBox_FindStringExact(hwnd, -1, toFind);

        if (CB_ERR == index)    //no match
        {
            // Find the first item in the combo box that starts with ToFind
            index = Combo_FindString(hwnd, -1, toFind);
        }
        if (CB_ERR != index)
        {
            // Else for match
            ComboBox_SetCurSel(hwnd, index);
            Combo_SetEditSel(hwnd, _tcslen(toFind), -1);
        }
		else
		{
			/* ComboBox_GetText(hwnd, buf, NELEMS(buf));
			 buf[_tcslen(buf)] = ch;
			 ComboBox_SetText(hwnd,buf);*/
			ComboBox_SetCurSel(hwnd, -1);
		}
			
    }
	
}

/// @brief Window procedure for the combobox control.
///
/// @param hwnd Handle of the combobox or a child edit control.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @returns LRESULT depends on message.

static LRESULT CALLBACK ComboBox_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static HWND hCombo;

    hCombo = GetParent(GetParent(hwnd));
    if (!IsExtended(hCombo)) hCombo = GetParent(hwnd);
	//BOOL bHandle = FALSE;
    switch(msg)
    {
        case WM_GETDLGCODE:
            return VK_TAB == wParam ? FALSE : DLGC_WANTALLKEYS;
        case WM_CHAR:
			//bHandle =(BOOL)lParam;
			//if(bHandle)
			//{
				DoAutoComplete(hCombo, (TCHAR)wParam);
			//	::SendMessage(hCombo,WM_CHAR,wParam,FALSE);
			//}
            break;
        case WM_DESTROY:  //Unsubclass the edit control
            SetWindowLongPtr(hwnd, GWLP_WNDPROC, (DWORD)GetProp(hwnd, TEXT("Wprc")));
            RemoveProp(hwnd, TEXT("Wprc"));
            break;
        default:
            return CallWindowProc((WNDPROC)GetProp(hwnd, TEXT("Wprc")), hwnd, msg, wParam, lParam);
    }
    return FALSE;
}

/// @brief Subclass a combobox control.
///
/// @param hComboBox Handle of the combobox to subclass.
///
/// @returns VOID.

void MakeAutocompleteCombo(HWND hComboBox)
{
    // SubClass the combo's Edit control
    HWND hEdit = IsExtended(hComboBox) ?
        ComboBoxEx_GetEditControl(hComboBox) :
            FindWindowEx(hComboBox, NULL, WC_EDIT, NULL);

    SetProp(hEdit, TEXT("Wprc"), (HANDLE)GetWindowLongPtr(hEdit, GWLP_WNDPROC));
    SubclassWindow(hEdit, ComboBox_Proc);

    // Set the text limit to standard size
    ComboBox_LimitText(hComboBox, DEFAULT_TXT_LIM);
}


void UnMakeAutocompleteCombo(HWND hComboBox)
{
    // SubClass the combo's Edit control
    HWND hEdit = IsExtended(hComboBox) ?
        ComboBoxEx_GetEditControl(hComboBox) :
            FindWindowEx(hComboBox, NULL, WC_EDIT, NULL);

    SetProp(hEdit, TEXT("Wprc"), NULL);
   
}
